package Utilities;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map.Entry;

/**
 * Database is a class which aims to provide a very basic implementation of data retrieval based 
 * on the pairing of table categories and data. An arbitrary number of fields can be specified
 * during the creation of the object and cannot be modified unless the database is recreated.
 * <p>
 * When retrieving data the user is able to specify the name of the field they wish to retrieve
 * and the index of the element they wish to get it from. The query function allows for the search
 * for a given element inside of the database and will return the relevant result(s).
 * 
 * @author Mike Hearing
 *
 */
public class Database {
	private HashMap<Integer, DBEntry> table;
	private HashMap<String, Integer> headers;
	private int length;
	public int num_rows;

	/**
	 * Creates a database object with headers based on the parameters passed. Types are not associated
	 * to each category yet, to retain genericness. ID of an entry is set to the key for the hash entry.
	 * 
	 * @param params Contains string names for each category desired in the database.
	 */
	public Database(String... params) {
		table = new HashMap<Integer, DBEntry>();
		headers = new HashMap<String, Integer>(params.length);

		for(int i = 0; i < params.length; i++)
			headers.put(params[i], i);

		length = params.length;
		num_rows = 0;
	}

	/**
	 * Creates a new entry in the database, which populates the data with as many parameters are provided.
	 * NULL may be passed in order to skip a parameter you don't want to enter yet.
	 *
	 * @param id Contains the reference to the entry being added
	 * @param o Contains objects representing each of the potential categories in the database
	 */
	public void addEntry(int id, Object... o) {
		table.put(id, new DBEntry(this.length, o));
		num_rows++;
	}

	/**
	 * Removes an entry from the database based on the specified reference key id.
	 *
	 * @param id Contains the reference to the entry being removed
	 */
	public void removeEntry(int id) {
		table.remove(id);
		num_rows--;
	}

	/**
	 * Provides a lookup for the corresponding numerical index of the specified header.
	 * Can be used in conjunction with manual access of the DBEntry object
	 * <p>
	 * Example: DBEntry.getData(Database.getHeaderID(header));
	 *
	 * @param header Contains a string representation of the header data you wish to retrieve the ID from.
	 * @return Numerical id corresponding to the String header
	 * @see DBEntry#getData
	 */
	public int getHeaderID(String header) {
		return headers.get(header);
	}

	/**
	 * Allows the editing of a single database entry by providing the id of the entry you wish to modify,
	 * and array of the headers you wish to edit, and the corresponding objects for each header. Additional
	 * objects provided in excess of headers will be ignored. Headers are paired by index to an object.
	 *
	 * @param id ID of the entry you wish to edit
	 * @param headers Contains all of the headers that will be provided a new value
	 * @param o Contains the corresponding objects to be stored for each header in headers
	 */
	public void editEntry(int id, String[] headers, Object[] o) {
		for(int i = 0; i < headers.length && i < o.length; i++)
			table.get(id).editData(getHeaderID(headers[i]), o[i]);
	}

	/**
	 * Allows the editing of multiple entries in the database by providing a value for a specific header.
	 * All occurences of Object value for String header in the database will have the corresponding
	 * headers reprlaced with the o of the same index. 
	 * <p>
	 * This method will call editEntry() for each match found.
	 *
	 * @param header Header that you wish to perform the query on
	 * @param value Object that is being queried for within field Header
	 * @param headers Contains all of the headers that will be provided a new value
	 * @param o Contains the corresponding objects to be stored for each header in headers
	 */
	public void editEntries(String header, Object value, String[] headers, Object[] o) {
		for(Entry<Integer, DBEntry> entry : table.entrySet())
			if(entry.getValue().getData(getHeaderID(header)) == value) {
				editEntry(entry.getKey(), headers, o);
			}
	}

	/**
	 * Retrieves a single element of data for String header from table entry id. Return should be cast from Object!
	 * 
	 * @param id ID of the entry you wish to retrieve and element from
	 * @param header Header of the data you wish to extract from the entry
	 * @return Object containing the data associated with the Strign header from entry id
	 */
	public Object getDataById(int id, String header) {
		return table.get(id).getData(getHeaderID(header));
	}

	/**
	 * Retrieves an ArrayList of entries from the database which contain a specific value in a specific header.
	 * This method emulates a query method found in a traditional database, but eliminates the need to parse
	 * a command from the user.
	 *
	 * @param header Header that you wish to search for the specific value
	 * @param value Object that you wish to find in the returned entry
	 * @return ArrayList<DBEntry> containing all of the results matching the query
	 */
	public ArrayList<DBEntry> getEntriesByHeader(String header, Object value)
	{
		ArrayList<DBEntry> result = new ArrayList<DBEntry>();

		for(Entry<Integer, DBEntry> entry : table.entrySet())
			if(entry.getValue().getData(getHeaderID(header)) == value)
				result.add(entry.getValue());

		return result;
	}


	public class DBEntry {
		private Object[] data;

		private DBEntry(int size) {
			data = new Object[size];
		}

		private DBEntry(int size, Object... params) {
			data = new Object[size];

			for(int i = 0; (i < size) && (i < params.length); i++)
				data[i] = params[i];
		}

		private void editData(int pos, Object o) {
			data[pos] = o;
		}

		public Object getData(int pos) {
			return data[pos];
		}
	}
}